//
// PicoMax
// Copyright 2012 Thomas W. Holtquist
// www.skewworks.com
//
// GPL 3.0 Licensed
//

using System;
using System.Threading;

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

using Skewworks.PicoMax;
using Skewworks.PicoMax.Graphics565;

namespace Skewworks.PicoMax.Controllers.SSD1351
{

    /// <summary>
    /// Main graphics hardware interface for PicoMax
    /// </summary>
    [Serializable]
    public class Graphics
    {

        #region Enumerations

        // from SSD1351 datasheet
        private enum Cmd
        {
            SetColumnAddress2 = 0x15,
            SetRowAddress2 = 0x75,
            WriteRAMCommand0 = 0x5C,
            ReadRAMCommand0 = 0x5D,
            SetRemapAndColorDepth1 = 0xA0,
            SetDisplayStartLine1 = 0xA1,
            SetDisplayOffset1 = 0xA2,
            SetDisplayModeOFF0 = 0xA4,
            SetDisplayModeALLON0 = 0xA5,
            SetDisplayModeNORMAL0 = 0xA6,
            SetDisplayModeINVERSE0 = 0xA7,
            FunctionSelection1 = 0xAB,
            SetSleepModeON0 = 0xAE,
            SetSleepModeOFF0 = 0xAF,
            SetResetAndPrechargePeriod1 = 0xB1,
            FrontClockDividerAndOscillatorFrequency1 = 0xB3,
            SetSegmentLowVoltage3 = 0xB4,
            SetGPIO1 = 0xB5,
            SetSecondPrechargePeriod1 = 0xB6,
            LookUpTableForGrayScalePulseWidth63 = 0xB8,
            UseBuiltinLinearLUT0 = 0xB9,
            SetPrechargeVoltage1 = 0xBB,
            SetVCOMHVoltage1 = 0xBE,
            SetContrastCurrentForColourABC3 = 0xC1,
            MasterContrastCurrentControl1 = 0xC7,
            SetMuxRatio1 = 0xCA,
            SetCommandLock1 = 0xFD
        }

        #endregion

        #region Variables

        // Hardware
        private SPI oled;
        private SPI.Configuration oledConfig;
        private OutputPort dc;
        private OutputPort rst;
        private byte[] _ba = new byte[1];
        private bool _sleepAfterSend = false;
        private int _sleeptime = 1;
        private int _screenWidth;
        private int _screenHeight;

        private PicoBitmap _screen;

        private bool _busy;

        #endregion

        #region Constructor

        public Graphics(Cpu.Pin ChipSelect, Cpu.Pin DC, Cpu.Pin RST, SPI.SPI_module SPIModule, int ScreenWidth, int ScreenHeight, bool CreateDefaultScreen = true, uint Clock_RateKHz = 18000)
        {
            // Instance OLED
            Debug.GC(true);
            oledConfig = new SPI.Configuration(ChipSelect, false, 0, 0, false, true, Clock_RateKHz, SPIModule);
            oled = new SPI(oledConfig);
            dc = new OutputPort(DC, true);
            rst = new OutputPort(RST, true);

            _screenHeight = ScreenHeight;
            _screenWidth = ScreenWidth;

            // Create default screen
            if (CreateDefaultScreen)
                _screen = new PicoBitmap(_screenWidth, _screenHeight);

            // Initialize OLED
            InitOLED();

        }

        #endregion

        #region Properties

        public PicoBitmap Screen
        {
            get { return _screen; }
        }

        public int ScreenHeight
        {
            get { return _screenHeight; }
        }

        public int ScreenWidth
        {
            get { return _screenWidth; }
        }

        #endregion

        #region Public Methods (Flush)

        /// <summary>
        /// Flushes the supplied image to hardware
        /// </summary>
        /// <param name="Image">Image to paint</param>
        public void Flush(PicoBitmap Image)
        {
            lock (Image)
                SendParams(Cmd.WriteRAMCommand0, Image.Pixels);
        }

        /// <summary>
        /// Flushes the specified area of the image to hardware
        /// </summary>
        /// <param name="Image">Image to draw</param>
        /// <param name="X">X location inside image</param>
        /// <param name="Y">Y location inside image</param>
        /// <param name="Width">Width to render</param>
        /// <param name="Height">Height to render</param>
        public void Flush(PicoBitmap Image, int X, int Y, int Width, int Height)
        {
            // Data buffer
            lock (Image)
            {
                byte[] buffer = new byte[Width * 2];
                int xOffset = X * 2;

                for (int i = Y; i < Height + Y; i++)
                {
                    // Set location
                    SendParams(Cmd.SetColumnAddress2, new byte[] { (byte)X });
                    SendParams(Cmd.SetRowAddress2, new byte[] { (byte)i });

                    // Copy data
                    Array.Copy(_screen.Pixels, xOffset + (i * _screenWidth * 2), buffer, 0, buffer.Length);

                    // Write data
                    SendParams(Cmd.WriteRAMCommand0, buffer);
                }

                SendParams(Cmd.SetColumnAddress2, new byte[] { 0 });
                SendParams(Cmd.SetRowAddress2, new byte[] { 0 });
            }
        }

        #endregion

        #region Private Methods

        private void InitOLED()
        {
            oled.Write(new byte[] { 0x00 });

            // Reset
            rst.Write(false);
            Thread.Sleep(100);
            rst.Write(true);
            Thread.Sleep(50);

            // Init
            _sleepAfterSend = true;

            SendParams(Cmd.SetCommandLock1, 0x12);
            SendParams(Cmd.SetCommandLock1, 0xB1);
            SendParams(Cmd.SetSleepModeON0);
            SendParams(Cmd.FrontClockDividerAndOscillatorFrequency1, 0xF1); // DIVSET = 1, Oscillator frequency = 15
            SendParams(Cmd.SetMuxRatio1, 0x7F);
            SendParams(Cmd.SetDisplayOffset1, 0x00);
            SendParams(Cmd.SetDisplayStartLine1, 0x00);
            SendParams(Cmd.SetRemapAndColorDepth1, 0x74); // 65K (16 bit) color
            SendParams(Cmd.SetGPIO1, 0x00);
            SendParams(Cmd.FunctionSelection1, 0x01); // internal VDD
            SendParams(Cmd.SetSegmentLowVoltage3, 0xA0, 0xB5, 0x55); // External VSL

            SendParams(Cmd.SetContrastCurrentForColourABC3, 0xC8, 0x80, 0xC8);
            SendParams(Cmd.MasterContrastCurrentControl1, 0x0F); // halve output currents 

            SendParams(Cmd.UseBuiltinLinearLUT0);

            SendParams(Cmd.SetResetAndPrechargePeriod1, 0x32); // minimum periods allowed
            SendParams((Cmd)0xB2, 0xA4, 0x00, 0x00); // "Enhance Driving Scheme Capability" - undocumented setting?
            SendParams(Cmd.SetPrechargeVoltage1, 0x17);
            SendParams(Cmd.SetSecondPrechargePeriod1, 0x01); // minimum period allowed
            SendParams(Cmd.SetVCOMHVoltage1, 0x05);
            SendParams(Cmd.SetDisplayModeNORMAL0);

            if (_screen != null)
                SendParams(Cmd.WriteRAMCommand0, _screen.Pixels);

            SendParams(Cmd.SetSleepModeOFF0);

            _sleepAfterSend = false;
            Debug.GC(true);
        }

        private void SendParams(Cmd cmd, params byte[] args)
        {
            Send((byte)cmd, args);
        }

        private void Send(Cmd cmd, byte[] args)
        {
            Send((byte)cmd, args);
        }

        private void Send(byte command, byte[] data)
        {
            _ba[0] = (byte)command;
            dc.Write(false);
            oled.Write(_ba);
            if (data != null && data.Length > 0)
            {
                dc.Write(true);
                oled.Write(data);
            }
            if (_sleepAfterSend) Thread.Sleep(_sleeptime);
        }

        #endregion

    }
}
